﻿namespace Neovolve.Toolkit.Workflow.Design
{
    using System;
    using System.Activities.Presentation;
    using System.Activities.Presentation.Model;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Windows;

    /// <summary>
    /// The instance resolver designer extension.
    /// </summary>
    public class InstanceResolverDesignerExtension
    {
        /// <summary>
        /// Defines the name of the Arguments property.
        /// </summary>
        internal const String Arguments = "Arguments";

        /// <summary>
        /// Defines the name of the ArgumentCount property.
        /// </summary>
        private const String ArgumentCount = "ArgumentCount";

        /// <summary>
        /// Stores the set of attached properties for this designer.
        /// </summary>
        private readonly Dictionary<String, AttachedProperty> _attachedProperties = new Dictionary<String, AttachedProperty>();

        /// <summary>
        /// Stores the value of the previous arguments value.
        /// </summary>
        private GenericArgumentCount _previousArguments;

        /// <summary>
        /// Attaches the specified model item.
        /// </summary>
        /// <param name="modelItem">
        /// The model item.
        /// </param>
        public static void Attach(ModelItem modelItem)
        {
            EditingContext editingContext = modelItem.GetEditingContext();
            InstanceResolverDesignerExtension designerExtension = editingContext.Services.GetService<InstanceResolverDesignerExtension>();

            if (designerExtension == null)
            {
                designerExtension = new InstanceResolverDesignerExtension();

                editingContext.Services.Publish(designerExtension);
            }

            designerExtension.AttachToModelItem(modelItem);
        }

        /// <summary>
        /// Attaches the argument visible property.
        /// </summary>
        /// <param name="attachedPropertiesService">
        /// The attached properties service.
        /// </param>
        /// <param name="modelItem">
        /// The model item.
        /// </param>
        /// <param name="argumentNumber">
        /// The argument number.
        /// </param>
        /// <returns>
        /// A <see cref="AttachedProperty&lt;T&gt;"/> instance.
        /// </returns>
        private static AttachedProperty<Visibility> AttachArgumentVisibleProperty(
            AttachedPropertiesService attachedPropertiesService, ModelItem modelItem, Int32 argumentNumber)
        {
            String propertyName;

            if (argumentNumber > 0)
            {
                propertyName = "Argument" + argumentNumber + "Visible";
            }
            else
            {
                propertyName = "ArgumentVisible";
            }

            AttachedProperty<Visibility> attachedProperty = new AttachedProperty<Visibility>
                                                            {
                                                                IsBrowsable = false, 
                                                                Name = propertyName, 
                                                                OwnerType = modelItem.ItemType, 
                                                                Getter = delegate(ModelItem modelReference)
                                                                         {
                                                                             Int32 argumentCount =
                                                                                 (Int32)modelReference.Properties[Arguments].ComputedValue;

                                                                             if (argumentNumber == 0 && argumentCount == 1)
                                                                             {
                                                                                 return Visibility.Visible;
                                                                             }

                                                                             if (argumentNumber != 0 && argumentCount > 1 &&
                                                                                 argumentNumber <= argumentCount)
                                                                             {
                                                                                 return Visibility.Visible;
                                                                             }

                                                                             return Visibility.Collapsed;
                                                                         }, 
                                                                Setter = delegate(ModelItem modelReference, Visibility newValue)
                                                                         {
                                                                             Debug.WriteLine("Visibility updated to " + newValue);
                                                                         }
                                                            };

            attachedPropertiesService.AddProperty(attachedProperty);

            return attachedProperty;
        }

        /// <summary>
        /// Attaches the arguments property.
        /// </summary>
        /// <param name="modelItem">
        /// The model item.
        /// </param>
        /// <param name="attachedPropertiesService">
        /// The attached properties service.
        /// </param>
        private void AttachArgumentsProperty(ModelItem modelItem, AttachedPropertiesService attachedPropertiesService)
        {
            AttachedProperty<GenericArgumentCount> argumentsProperty = new AttachedProperty<GenericArgumentCount>
                                                                       {
                                                                           Name = Arguments, 
                                                                           IsBrowsable = true, 
                                                                           OwnerType = modelItem.ItemType, 
                                                                           Getter = delegate(ModelItem modelReference)
                                                                                    {
                                                                                        return
                                                                                            (GenericArgumentCount)
                                                                                            modelReference.Properties[ArgumentCount].ComputedValue;
                                                                                    }, 
                                                                           Setter = delegate(ModelItem modelReference, GenericArgumentCount newValue)
                                                                                    {
                                                                                        _previousArguments =
                                                                                            (GenericArgumentCount)
                                                                                            modelReference.Properties[ArgumentCount].ComputedValue;

                                                                                        modelReference.Properties[ArgumentCount].ComputedValue =
                                                                                            newValue;

                                                                                        // Update the activity to use InstanceResolver with the correct number of arguments
                                                                                        UpgradeInstanceResolverType(modelReference);
                                                                                    }
                                                                       };

            attachedPropertiesService.AddProperty(argumentsProperty);
        }

        /// <summary>
        /// Attaches the specified model item.
        /// </summary>
        /// <param name="modelItem">
        /// The model item.
        /// </param>
        private void AttachToModelItem(ModelItem modelItem)
        {
            // Get the number of arguments from the model
            GenericArgumentCount argumentCount = (GenericArgumentCount)modelItem.Properties[ArgumentCount].ComputedValue;
            EditingContext editingContext = modelItem.GetEditingContext();
            AttachedPropertiesService attachedPropertiesService = editingContext.Services.GetService<AttachedPropertiesService>();

            // Store the previous argument count value to track the changes to the property
            _previousArguments = argumentCount;

            // Attach updatable type arguments
            InstanceResolverTypeUpdater.AttachUpdatableArgumentTypes(modelItem, (Int32)argumentCount);

            AttachArgumentsProperty(modelItem, attachedPropertiesService);

            // Start from the second argument because there must always be at least one instance resolution per InstaceResolver
            for (Int32 index = 0; index <= 16; index++)
            {
                // Create an attached property that calculates the designer visibility for a stack panel
                AttachedProperty<Visibility> argumentVisibleProperty = AttachArgumentVisibleProperty(attachedPropertiesService, modelItem, index);

                _attachedProperties[argumentVisibleProperty.Name] = argumentVisibleProperty;
            }
        }

        /// <summary>
        /// Upgrades the type of the instance resolver.
        /// </summary>
        /// <param name="modelItem">
        /// The model item.
        /// </param>
        private void UpgradeInstanceResolverType(ModelItem modelItem)
        {
            Type[] originalGenericTypes = modelItem.ItemType.GetGenericArguments();
            Type[] genericTypes = new Type[16];

            originalGenericTypes.CopyTo(genericTypes, 0);

            if (originalGenericTypes.Length < genericTypes.Length)
            {
                Type defaultGenericType = typeof(Object);

                // This type is being upgraded from InstanceResolver to InstanceResolver
                // Initialize the generic types with Object
                for (Int32 index = originalGenericTypes.Length; index < genericTypes.Length; index++)
                {
                    genericTypes[index] = defaultGenericType;
                }
            }

            Type newType = RegisterMetadata.InstanceResolverT16GenericType.MakeGenericType(genericTypes);

            InstanceResolverTypeUpdater.UpdateModelType(modelItem, newType, _previousArguments);
        }
    }
}